<template>
  <div class="container">
    <div class="editor" ref="el"></div>
    <div class="width-bar" ref="bar" v-show="isResultShown" @mousedown="onMouseDown">...</div>
    <div class="editor" ref="el2" v-show="isResultShown"></div>
  </div>
  <div class="icon play" title="运行 (Ctrl+R)" @click="run()"></div>
  <div
    class="icon"
    v-bind:class="{ show: !isResultShown, hide: isResultShown }"
    :title="!isResultShown ? '显示日志区 (F12)' : '隐藏日志区 (F12)'"
    @click="toggleConsole()"
  ></div>
</template>

<script lang="ts">
/* eslint-disable @typescript-eslint/no-explicit-any */

import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import { format } from 'date-fns';
import * as monaco from 'monaco-editor';
import { debounce, onSystemReady } from '../utils/common';

function createEditor(
  el: HTMLElement,
  options: monaco.editor.IStandaloneEditorConstructionOptions = {},
) {
  const editor = monaco.editor.create(el, {
    language: 'javascript',
    theme: 'vs-dark',
    tabSize: 2,
    minimap: { enabled: false },
    automaticLayout: true,
    ...options,
  });
  return {
    getValue: () => editor.getValue(),
    setValue: (d: string) => editor.setValue(d),
    addAction: (d: any) => editor.addAction(d),
    getLineCount: () => editor.getModel()?.getLineCount(),
    revealLine: (d: number) => editor.revealLine(d),
    format: () => editor.getAction('editor.action.formatDocument').run(),
    getModel: () => editor.getModel(),
    getSelection: () => editor.getSelection(),
    getScrollTop: () => editor.getScrollTop(),
    setScrollTop: (e: number) => editor.setScrollTop(e),
    dispose: () => editor.dispose(),
  };
}

let { localStorage } = window;
let editorWidth = 50;

export default defineComponent({
  name: 'HelloWorld',
  setup() {
    const el = ref();
    const el2 = ref();
    const bar = ref();
    const editor = ref();
    const result = ref();
    const isResultShown = ref(true);

    const genericLogger = () => {
      return function () {
        if (result.value) {
          const value = [].slice
            .call(arguments)
            .reduce(
              (prev, arg) => `${prev} ${typeof arg === 'object' ? JSON.stringify(arg) : arg}`,
              '',
            );
          const time = `[${format(new Date(), 'HH:mm:ss.SSS')}] `;
          result.value.setValue(result.value.getValue() + time + value + '\n');
          result.value.revealLine(result.value.getLineCount() || 0);
        }
      };
    };

    const initLogger = () => {
      console.log = function () {
        return genericLogger().apply(null, arguments as any);
      };
      console.info = function () {
        return genericLogger().apply(null, arguments as any);
      };
      console.warn = function () {
        return genericLogger().apply(null, arguments as any);
      };
      console.error = function () {
        return genericLogger().apply(null, arguments as any);
      };
    };

    const initEditor = () => {
      if (el.value) {
        editor.value = createEditor(el.value);
        editor.value.addAction({
          id: 'run', // 菜单项 id
          label: '运行', // 菜单项名称
          keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_R], // 绑定快捷键
          contextMenuGroupId: 'navigation', // 所属菜单的分组
          run: () => run(), // 点击后执行的操作
        });
        editor.value.addAction({
          id: 'F12', // 菜单项 id
          label: '日志区', // 菜单项名称
          keybindings: [monaco.KeyCode.F12], // 绑定快捷键
          contextMenuGroupId: 'navigation', // 所属菜单的分组
          run: () => toggleConsole(), // 点击后执行的操作
        });
        editor.value.getModel().onDidChangeContent(
          debounce(() => {
            localStorage.setItem('value', editor.value.getValue());
          }, 500),
        );
        editor.value.setValue(localStorage.getItem('value') || '');
      }
      if (el2.value) {
        result.value = createEditor(el2.value, { readOnly: true });
        result.value.addAction({
          id: 'F12', // 菜单项 id
          label: '日志区', // 菜单项名称
          keybindings: [monaco.KeyCode.F12], // 绑定快捷键
          contextMenuGroupId: 'navigation', // 所属菜单的分组
          run: () => toggleConsole(), // 点击后执行的操作
        });
      }
    };

    const initEditorWidth = () => {
      setEditorWidth(+(localStorage.getItem('width') || 50));
    };

    const setEditorWidth = (w: number) => {
      el.value.style.width = `calc(${w}vw - 4px)`;
      el2.value.style.width = `calc(${100 - w}vw - 4px)`;
      editorWidth = w;
    };

    const onMouseMove = ({ pageX }: MouseEvent) => {
      const w = Math.floor((pageX / document.body.clientWidth) * 100);
      if (w > 25 && w < 75) {
        setEditorWidth(w);
      }
    };

    const onMouseUp = () => {
      localStorage.setItem('width', editorWidth + '');
      bar.value.classList.remove('hover');
      document.body.style.cursor = 'default';
      document.removeEventListener('mousemove', onMouseMove);
      document.removeEventListener('mouseup', onMouseUp);
    };

    const onMouseDown = () => {
      bar.value.classList.add('hover');
      document.body.style.cursor = 'e-resize';
      document.addEventListener('mousemove', onMouseMove);
      document.addEventListener('mouseup', onMouseUp);
    };

    const run = () => {
      if (!isResultShown.value) {
        toggleConsole();
      }
      if (editor.value) {
        try {
          editor.value.format(); // 格式化代码区
          result.value.setValue(''); // 清空结果区
          const selection = editor.value.getSelection(); // 获取选中范围
          const value = editor.value.getModel().getValueInRange(selection); // 获取选中代码
          Function(`(function(){\n${value || editor.value.getValue()}\n})()`)(); // 执行代码
          if (result.value.getScrollTop()) {
            result.value.setScrollTop(0); // 置顶日志区滚动条
          }
        } catch (error) {
          console.log(error.message);
        }
      }
    };

    const toggleConsole = () => {
      isResultShown.value = !isResultShown.value;
      if (isResultShown.value) {
        initEditorWidth();
      } else {
        el.value.style.width = `100vw`;
      }
    };

    onMounted(async () => {
      await onSystemReady();
      const { utools } = window as any;
      if (utools) {
        localStorage = utools.dbStorage;
      }
      initLogger();
      initEditor();
      initEditorWidth();
    });

    onUnmounted(() => {
      if (editor.value) {
        editor.value.dispose();
      }
      if (result.value) {
        result.value.dispose();
      }
    });

    return { el, el2, bar, isResultShown, run, toggleConsole, onMouseDown };
  },
});
</script>

<style scoped lang="less">
.container {
  display: flex;
}
.editor {
  width: calc(50vw - 4px);
  height: 100vh;
}
.width-bar {
  display: flex;
  align-items: center;
  text-align: center;
  box-sizing: border-box;
  margin-left: -1px;
  padding-left: 1px;
  width: 8px;
  background-color: #111;
  color: #666;
  font-size: 24px;
  line-height: 10px;
  word-break: break-word;
  cursor: e-resize;
  user-select: none;
  &.hover,
  &:hover {
    background-color: #333;
    color: #999;
  }
}
.play {
  bottom: 24px;
  right: 40px;
  background: url('../assets/play.png');
}
.show {
  bottom: 74px;
  right: 40px;
  background: url('../assets/show.png');
}
.hide {
  bottom: 74px;
  right: 40px;
  background: url('../assets/hide.png');
}
.icon {
  position: fixed;
  width: 40px;
  height: 40px;
  background-size: cover;
  cursor: pointer;
}
</style>
